/**
 * Copyright 2006 Brad Reynolds
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.leeloo.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Manages the storage and retrieval of tags.
 * 
 * @author Brad Reynolds
 */
public class TagService {
    /**
     * Map of arrays of Tags.
     * 
     * <dl>
     * <dt>key</dt>
     * <dd>object</dd>
     * <dt>value</dt>
     * <dd>array of Tags sorted by tag name</dd>
     * </dl>
     */
    private Map tagsByObject = new HashMap();

    /**
     * Empty array to represent no tags.
     */
    private final static Tag[] NO_TAGS = new Tag[0];

    /**
     * Constructs a new instance.
     */
    public TagService() {
    }

    /**
     * @param object
     * @param name
     * @return <code>true</code> if a tag exists for the provided
     *         <code>name</code>
     */
    public boolean contains(Object object, String name) {
        Tag[] tags = getTags(object);

        return (Arrays.binarySearch(tags, new Tag(name, "")) > -1); //$NON-NLS-1$
    }

    /**
     * @param object
     * @return tags that have been set for the provided <code>object</code>,
     *         empty array if none
     */
    public Tag[] getAll(Object object) {
        Tag[] tags = getTags(object);
        Tag[] result = null;

        if (tags.length > 0) {
            result = new Tag[tags.length];
            System.arraycopy(tags, 0, result, 0, tags.length);
        }

        return (result != null) ? result : NO_TAGS;
    }

    /**
     * @param object
     * @param name
     * @return tag for the provided <code>object</code> with the provided
     *         <code>name</code>, <code>null</code> if none.
     */
    public Tag get(Object object, String name) {
        Tag[] tags = getTags(object);

        int index = Arrays.binarySearch(tags, new Tag(name, "")); //$NON-NLS-1$

        return (index > -1) ? tags[index] : null;
    }

    /**
     * @param object
     * @return tags for the object, 0 length array for none
     */
    private Tag[] getTags(Object object) {
        Tag[] tags = (Tag[]) tagsByObject.get(object);
        return (tags != null) ? tags : NO_TAGS;
    }

    /**
     * @param object
     * @return number of tags that have been set for the provided
     *         <code>object</code>.
     */
    public int getTagCount(Object object) {
        return getTags(object).length;
    }

    /**
     * Registers the provided <code>tag</code> for the provided
     * <code>object</code>. Only one tag can be registered per name per
     * object.
     * 
     * @param object
     * @param tag
     * @return tag if the provided <code>tag</code> replaced an existing tag
     *         with the same name, <code>null</code> if previous did not exist
     */
    public Tag put(Object object, Tag tag) {
        Tag[] tags = getTags(object);
        Tag result = null;

        if (tags.length > 0) {
            int index = Arrays.binarySearch(tags, tag);
            if (index > -1) {
                result = tags[index];
                tags[index] = tag;
            } else {
                Tag[] newTags = new Tag[tags.length + 1];
                System.arraycopy(tags, 0, newTags, 0, tags.length);
                tags = newTags;
                tags[tags.length - 1] = tag;
                Arrays.sort(tags);
            }
        } else {
            tags = new Tag[] { tag };
        }

        tagsByObject.put(object, tags);

        return result;
    }

    /**
     * Removes tag with associated with the provided <code>object</code> that
     * has the provided <code>name</code>.
     * 
     * @param object
     * @param name
     * @return removed tag, <code>null</code> if not found
     */
    public Tag remove(Object object, String name) {
        Tag[] tags = getTags(object);
        Tag result = null;

        int index = Arrays.binarySearch(tags, new Tag(name, "")); //$NON-NLS-1$
        if (index > -1) {
            result = tags[index];
            ArrayList list = new ArrayList(tags.length);
            list.addAll(Arrays.asList(tags));
            list.remove(index);
            tags = (Tag[]) list.toArray(new Tag[list.size()]);
            tagsByObject.put(object, tags);
        }

        return result;
    }
}
